/*
 *  policy - Part of the Policy Server Application
 *  Copyright (C) 2009 Liam Lowey
 *  
 *      http://policyserver.turnitin.co.uk/
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at 
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0 
 *  
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */

using System;
using System.Net.Sockets;

namespace policyserver
{
  public class Connection
  {
    private static readonly int READ_BUFFER_SIZE = 256;
    private static readonly string[] ACCEPTABLE_TRIGGERS = new string[] 
    {
      @"<policy-file-request />",
      @"<policy-file-request/>",
      "<policy-file-request />\r\n"
    };

    private Socket _sock;
    private bool _isClosed;
    private bool _dataToWrite;
    private byte[] _payload;
    private int _writtenSoFar = 0;
    private string _readSoFar = "";

    public Connection(Socket sock, byte[] payload)
    {
      _sock = sock;
      _payload = payload;
      _sock.Blocking = true;

      trace( "Connection() - Accepted connection from {0}.", sock.RemoteEndPoint.ToString() );
    }


    ~Connection()
    {
      close();
    }


    public void close()
    {
      if( !_isClosed )
      {
        trace( "close() - Closing socket to {0}.", _sock.RemoteEndPoint.ToString() );
        _isClosed = true;
        _sock.Shutdown( SocketShutdown.Both );
        _sock.Close();
      }
    }


    public bool processRead()
    {
      byte[] buffer = new byte[READ_BUFFER_SIZE];

      SocketError error = SocketError.Success;

      int readThisTime = _sock.Receive( buffer, 0, READ_BUFFER_SIZE, SocketFlags.Partial, out error );

      if( readThisTime == 0 )
      {
        trace( "processRead() - Socket returned 0 bytes; closed by remote host." );
        close();
        return false;
      }

      if( error != SocketError.Success )
      {
        trace( "processRead() - Socket returned error: {0}; closing.", error );
        close();
        return false;
      }

      for( int by = 0; by < readThisTime; ++by )
      {
        if( buffer[by] == 0 )
        {
          break;// we expect a null to complete the message
        }

        _readSoFar += (char)buffer[by];
      }

      switch(checkPartialRead(_readSoFar))
      {
      case CheckResult.Success:
        trace( "processRead() - Sending policy file" );
        return sendPacket();
      case CheckResult.Partial:
        trace( "processRead() - Read {0} bytes, total: {1} bytes.", readThisTime, _readSoFar.Length );
        return true;
      case CheckResult.Fail:
      default:
        trace( "processRead() - Error in packet: {0}.", _readSoFar );
        close();
        return false;
      }
    }


    private enum CheckResult
    {
      Success = 0,
      Partial = 1,
      Fail = 2
    };


    private CheckResult checkPartialRead( string partialRead )
    {
      if( partialRead.Length > 0 )
      {
        foreach( string trigger in ACCEPTABLE_TRIGGERS )
        {
          if( trigger == partialRead )
          {
            return CheckResult.Success;
          }
          else if( trigger.StartsWith( partialRead, StringComparison.Ordinal ) )
          {
            return CheckResult.Partial;
          }
        }

        return CheckResult.Fail;
      }
      return CheckResult.Partial;
    }


    public bool processWrite()
    {
      return sendPacket();
    }


    private bool sendPacket()
    {
      SocketError error = SocketError.Success;

      int leftToSend = _payload.Length - _writtenSoFar;

      int sentThisTime = _sock.Send( _payload, 
                                     _writtenSoFar, 
                                     leftToSend, 
                                     SocketFlags.Partial, 
                                     out error );

      if( 0 == sentThisTime )
      {
        trace( "sendPacket() - Socket closed by remote host." );
        return false;
      }

      if( sentThisTime == leftToSend )
      {
        trace( "sendPacket() - Policy sent, closing." );

        // Sucesfully completed sending, so close the connection
        // and tell the reactor to remove this connection object.
        _dataToWrite = false;
        close();
        return false;
      }

      // incomplete sending, so:
      _dataToWrite = true;
      _writtenSoFar += sentThisTime;

      trace( "sendPacket() - Sent: {0} Remaining: {1} Total: {2}.",
            sentThisTime,
            (_payload.Length - _writtenSoFar),
            _payload.Length );

      return true;
    }


    public Socket socket
    {
      get { return _sock; }
    }

    public bool isWritable
    {
      get { return _dataToWrite; }
    }

    private void trace( string format, params object[] args )
    {
      System.Console.WriteLine( format, args );
    }
  }
}
